package com.example.fabric;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.Reader;
import java.io.Serializable;
import java.io.StringReader;
import java.security.PrivateKey;
import java.security.Security;
import java.util.*;

import com.example.fabric.config.FabricConfig;
import org.apache.commons.io.IOUtils;
import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.openssl.PEMParser;
import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter;
import org.hyperledger.fabric.sdk.*;
import org.hyperledger.fabric.sdk.ChaincodeResponse.Status;
import org.hyperledger.fabric.sdk.exception.InvalidArgumentException;
import org.hyperledger.fabric.sdk.exception.ProposalException;
import org.springframework.beans.factory.annotation.Autowired;

public class FabricDemo {

    @Autowired
    private FabricConfig fabricConfig;

    static {
        Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
    }

    //调用已经部署的chaincode的invoke方法
    public static void sendTranstionTx(HFClient client, Channel channel, ChaincodeID ccID, User user, Long timeout) throws ProposalException, InvalidArgumentException {
        String funcName = "invoke";
        sendTranstion(client, channel, ccID, user, timeout, "invoke",  "a", "b", "1");
    }

    public static void sendTranstionEvd(HFClient client, Channel channel, ChaincodeID ccID, User user, Long timeout) throws ProposalException, InvalidArgumentException {
        String funcName = "";
        String content = "{\"evdID\":\"123456\",\"owner\":\"Jhson\",\"content\":\"abcdef\",\"createDate\":\"2020-04-12\"}\n";
        sendTranstion(client, channel, ccID, user, timeout, funcName, content);
    }

    public static void sendTranstion(HFClient client, Channel channel, ChaincodeID ccID, User user, Long timeout, String funcName, String... content) {
        Collection<ProposalResponse> successful = new LinkedList<>();
        TransactionProposalRequest transactionProposalRequest = client.newTransactionProposalRequest();
        transactionProposalRequest.setChaincodeID(ccID);
        transactionProposalRequest.setFcn(funcName);
        transactionProposalRequest.setArgs(content);
        transactionProposalRequest.setProposalWaitTime(timeout);
        transactionProposalRequest.setUserContext(user);
        try {
            Collection<ProposalResponse> invokePropResp = channel.sendTransactionProposal(transactionProposalRequest);
            for (ProposalResponse pr : invokePropResp) {
                System.out.println("peer name" + pr.getPeer().getName());
                if (pr.getStatus() == Status.SUCCESS) {
                    System.out.printf("successful transaction proposal response Txid : %s from peer: %s\n", pr.getTransactionID(), pr.getPeer().getName());
                    successful.add(pr);
//                System.out.println("---------------------------------" + pr.getProposalResponse().getPayload().toByteArray());
                    System.out.println("---------------------------------" + pr.getProposalResponse().getResponse().getStatus());
                } else {
                    System.out.printf("fail %s\n", pr.getMessage());
                }
            }
            System.out.println();
            channel.sendTransaction(successful, user);
        } catch (ProposalException | InvalidArgumentException e) {
            e.printStackTrace();
        }
    }

    public static String QueryChaincode(HFClient client, Channel channel, ChaincodeID ccID, String funcName, String... condition) throws ProposalException, InvalidArgumentException {
        QueryByChaincodeRequest queryByChaincodeRequest = client.newQueryProposalRequest();
        queryByChaincodeRequest.setArgs(condition);
        queryByChaincodeRequest.setFcn(funcName);
        queryByChaincodeRequest.setChaincodeID(ccID);
        Collection<ProposalResponse> queryProposals = channel.queryByChaincode(queryByChaincodeRequest);
        String prePayload = "";
        boolean first = true;
        for (ProposalResponse pr : queryProposals) {
            if (!pr.isVerified() || pr.getStatus() != ChaincodeResponse.Status.SUCCESS) {
                System.out.println("Failed query proposal from peer(" + pr.getPeer().getName() + ")!");
            } else {
                String payload = pr.getProposalResponse().getResponse().getPayload().toStringUtf8();
                if (!first) {
                    if (!payload.equals(prePayload)) {
                        return "";
                    }
                } else {
                    prePayload = payload;
                }

                System.out.println("peer(" + pr.getPeer().getName() + ") Query a success : " + payload);
            }
        }
        return prePayload;
    }

    /**
     * 根据cryptogen模块生成的账号创建fabric账号
     *
     * @param username
     * @param org
     * @param orgId
     * @return
     */
    public static User getFabricUser4Local(String username, String org, String orgId, String certFile, String skFile) {
        FabricUserImpl user = new FabricUserImpl(username, org);
        user.setMspId(orgId);

        try {
            String certificate = new String(IOUtils.toByteArray(new FileInputStream(certFile)));
            File privateKeyFile = new File(skFile);
            PrivateKey pk = getPrivateKeyFromBytes(IOUtils.toByteArray(new FileInputStream(privateKeyFile)));

            EnrollmentImpl enrollement = new EnrollmentImpl(pk, certificate);
            user.setEnrollment(enrollement);
        } catch (IOException e) {
            e.printStackTrace();
        }

        return user;
    }

    private static PrivateKey getPrivateKeyFromBytes(byte[] data) throws IOException {
        final Reader pemReader = new StringReader(new String(data));
        final PrivateKeyInfo pemPair;
        PEMParser pemParse = new PEMParser(pemReader);
        pemPair = (PrivateKeyInfo) pemParse.readObject();
        PrivateKey pk = new JcaPEMKeyConverter().setProvider(BouncyCastleProvider.PROVIDER_NAME).getPrivateKey(pemPair);
        pemParse.close();
        return pk;
    }

    static final class EnrollmentImpl implements Enrollment, Serializable {

        private static final long serialVersionUID = 1L;
        private final PrivateKey privateKey;
        private final String certificate;

        public EnrollmentImpl(PrivateKey pk, String c) {
            this.privateKey = pk;
            this.certificate = c;
        }

        @Override
        public PrivateKey getKey() {
            return privateKey;
        }

        @Override
        public String getCert() {
            return certificate;
        }

    }

    static final class FabricUserImpl implements User, Serializable {

        private static final long serialVersionUID = 1L;
        private String name;
        private Set<String> roles;
        private String account;
        private String affiliation;
        private String organization;
        private String enrollmentSecret;
        Enrollment enrollment = null;

        public FabricUserImpl(String name, String org) {
            this.name = name;
            this.organization = org;
        }

        @Override
        public String getName() {
            return this.name;
        }

        @Override
        public Set<String> getRoles() {
            return this.roles;
        }

        public void setRoles(Set<String> roles) {
            this.roles = roles;
        }

        @Override
        public String getAccount() {
            return this.account;
        }

        public void setAccount(String account) {
            this.account = account;
        }

        @Override
        public String getAffiliation() {
            return this.affiliation;
        }

        public void setAffiliation(String af) {
            this.affiliation = af;
        }

        @Override
        public Enrollment getEnrollment() {
            return this.enrollment;
        }

        public boolean isEnrolled() {
            return this.enrollment != null;
        }

        public String getEnrollmentSecret() {
            return this.enrollmentSecret;
        }

        public void setEnrollmentSecret(String es) {
            this.enrollmentSecret = es;
        }

        public void setEnrollment(Enrollment e) {
            this.enrollment = e;
        }

        String mspId;

        @Override
        public String getMspId() {
            return this.mspId;
        }

        public void setMspId(String mspId) {
            this.mspId = mspId;
        }
    }
}
